<?php

namespace SqlListen;

use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\ServiceProvider;

class SqlListenServiceProvider extends ServiceProvider
{
    /**
     * Perform post-registration booting of services.
     *
     * @return void
     */
    public function boot(): void
    {
        // Publishing is only necessary when using the CLI.
        if ($this->app->runningInConsole()) {
            $this->bootForConsole();
        }
        if(config("sqllisten.open_listen_sql") && is_dir(config("sqllisten.sql_log_path"))){
            $traceId=$_REQUEST['trace_id']??SqlListenServiceProvider::random(40);
            \DB::listen(function (QueryExecuted $sql) use ($traceId){
                if($slowTimeLimit=config("sqllisten.slow_time_limit")){
                   if(is_numeric($slowTimeLimit)){
                       if($sql->time<$slowTimeLimit){
                           return;
                       }
                   }
                }

                foreach ($sql->bindings as $i => $binding) {
                    if ($binding instanceof \DateTime) {
                        $sql->bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
                    } else {
                        if (is_string($binding)) {
                            $sql->bindings[$i] = "'$binding'";
                        }
                    }
                }
                $trace_sql = str_replace(array('%', '?'), array('%%', '%s'), $sql->sql);
                $trace_sql = str_replace(array("\r\n", "\r", "\n")," ",$trace_sql);
                $trace_sql = vsprintf($trace_sql, $sql->bindings);
                foreach($sql->bindings as $key=>$val){
                    if(!is_numeric($key)) {
                        $trace_sql = str_replace(":" . $key, $val, $trace_sql);
                    }
                }

                $dbConf=$sql->connection->getConfig();
                if(isset($dbConf['password'])){
                    unset($dbConf['password']);
                }
                $logPath=config("sqllisten.sql_log_path").DIRECTORY_SEPARATOR.config("app.name").".sql_trace.".now()->toDateString().".log";
                $levelArr=[3000=>'FATAL',2000=>'ERROR',1000=>'WARNING'];
                $level='INFO';
                foreach($levelArr as $key=>$v){
                    if($sql->time>=$key){
                        $level=$v;
                        break;
                    }
                }

                file_put_contents($logPath,json_encode([
                        'level'=>$level,
                        'trace_id'=>$traceId,
                        'trace_time'=>now()->toDateTimeString(),
                        'app_env'=>config('app.env')??"",
                        'app_name'=>config('app.name')??"",
                        'req_uri'=>request()->url()??"",
                        'req_method'=>request()->method()??"",
                        'req_body'=>request()->all()??null,
                        'sql_exec_time'=>now()->format('Y-m-d H:i:s.u'),
                        'sql_uuid'=>SqlListenServiceProvider::random(40),
                        'db_connection_name'=>$sql->connectionName,
                        'db_conf'=>$dbConf,
                        'trace_sql'=>$sql->sql,
                        'trace_sql_bindings'=>$sql->bindings,
                        'execute_sql'=>$trace_sql,
                        'execute_ms'=>$sql->time
                    ]).PHP_EOL,FILE_APPEND);
            });
        }
    }

    public static function random($length = 16)
    {
        $string = '';

        while (($len = strlen($string)) < $length) {
            $size = $length - $len;

            $bytes = random_bytes($size);

            $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
        }

        return $string;
    }

    /**
     * Register any package services.
     *
     * @return void
     */
    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__.'/../config/sqllisten.php', 'sqllisten');
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['sqllisten'];
    }

    /**
     * Console-specific booting.
     *
     * @return void
     */
    protected function bootForConsole(): void
    {
        // Publishing the configuration file.
        $this->publishes([
            __DIR__.'/../config/sqllisten.php' => config_path('sqllisten.php'),
        ], 'sqllisten.config');
    }
}
